// --------------------------------------------------------------------------------------------------------------------
// <copyright file="TcpCom.cs" company="Microsoft Corporation">
// The MIT License (MIT)
// 
// Copyright (c) 2014, Microsoft Corporation
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Robotics.Navigation.Benchmark
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Net.Sockets;
    using System.Runtime.InteropServices;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;

    /// <summary>
    /// Class for communicating on a Socket
    /// </summary>
    public class TcpCom
    {
        /// <summary>
        /// message buffer size
        /// </summary>
        private const int BufferSize = 1024;

        /// <summary>
        /// listener for new connections
        /// </summary>
        private TcpListener listener;

        /// <summary>
        /// communications client (uses socket underneath)
        /// </summary>
        private TcpClient client;

        /// <summary>
        /// User supplied message handler
        /// </summary>
        private Action<byte[]> handler;

        /// <summary>
        /// Converts an object to array of bytes
        /// </summary>
        /// <param name="obj">The input object</param>
        /// <param name="size">The size of the object</param>
        /// <returns>A byte array representing the object</returns>
        public static byte[] ConvertToByteArray(object obj, int size)
        {
            byte[] bytes = new byte[size];
            IntPtr ptr = Marshal.AllocHGlobal(size);
            Marshal.StructureToPtr(obj, ptr, true);
            Marshal.Copy(ptr, bytes, 0, size);
            Marshal.FreeHGlobal(ptr);
            return bytes;
        }

        /// <summary>
        /// Initializes a socket listening on the specified port
        /// </summary>
        /// <param name="port">The port to listen on</param>
        /// <param name="handler">The handler for messages on the socket</param>
        public async void StartListening(int port, Action<byte[]> handler)
        {
            CancellationTokenSource cts = new CancellationTokenSource();

            this.handler = handler;
            
            try
            {
                this.listener = new TcpListener(IPAddress.Any, port);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
                return;
            }

            // keep trying to reconnect
            while (this.listener != null)
            {
                this.listener.Start();
                Console.WriteLine("Listening for connection on port {0}...", port);
                try
                {
                    this.client = await this.listener.AcceptTcpClientAsync();
                }
                catch
                {
                    Console.WriteLine("Unable to continue listening for tcp clients.");
                    break;
                }

                Console.WriteLine("Tcp client connected!\n");
                using (NetworkStream stream = this.client.GetStream())
                {
                    byte[] buffer = new byte[BufferSize];
                    try
                    {
                        while (stream.CanRead)
                        {
                            Task<int> count = stream.ReadAsync(buffer, 0, BufferSize);
                            await count;
                            if (count.Result > 0)
                            {
                                this.handler(buffer);
                            }
                            else
                            {
                                break;
                            }
                        }

                        stream.Close();
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine(e.Message);
                    }
                }

                this.StopListening();
            }
        }

        /// <summary>
        /// Stop the listener
        /// </summary>
        public void StopListening()
        {
            this.listener.Stop();
            if (this.client != null)
            {
                this.client.Close();
                Console.WriteLine("Tcp client disconnected.");
            }
        }

        /// <summary>
        /// Send a single byte on the open stream.
        /// This is used to signal completion to the client
        /// </summary>
        /// <param name="reply">The byte to send as a reply</param>
        public void SendReply(byte reply)
        {
            if (this.client == null)
            {
                return;
            }
            
            try
            {
                NetworkStream stream = this.client.GetStream();
                if (stream.CanWrite)
                {
                    this.client.GetStream().WriteByte(reply);
                }
            }
            catch (InvalidOperationException e)
            {
                Console.WriteLine("Error: Cannot send reply to client. The network stream's client is not connected!\n{0}", e.Message);
            }            
        }
    }
}
